import os
import pathlib
from typing import List, Dict, Any, Optional, Callable
import logging

# 配置日志
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler('logs/scanner.log'),
        logging.StreamHandler()
    ]
)
logger = logging.getLogger('scanner')


class Scanner:
    def __init__(self,
                 progress_callback: Optional[Callable[[int, int], None]] = None,
                 file_callback: Optional[Callable[[Dict[str, Any]], None]] = None):
        """
        初始化扫描器
        :param progress_callback: 进度回调函数，接收已扫描文件数和总文件数
        :param file_callback: 文件信息回调函数，接收文件信息字典
        """
        self.progress_callback = progress_callback
        self.file_callback = file_callback
        self._is_scanning = False
        self._is_paused = False
        self._cancelled = False
        self._total_files = 0
        self._scanned_files = 0
        self._scanned_dirs = []  # 存储目录信息

    def _is_directory_empty(self, directory_path: str, ignore_patterns: Optional[List[str]] = None) -> bool:
        """
        检查目录是否为空
        :param directory_path: 目录路径
        :param ignore_patterns: 要忽略的文件模式列表
        :return: True如果目录为空，False否则
        """
        try:
            items = os.listdir(directory_path)
            if not items:
                return True  # 目录完全为空
            
            # 检查是否所有项目都被忽略
            for item in items:
                item_path = os.path.join(directory_path, item)
                if not self._should_ignore(item_path, ignore_patterns):
                    return False  # 找到一个不被忽略的项目
            
            return True  # 所有项目都被忽略，视为空目录
        except PermissionError:
            logger.warning(f'无权限访问目录: {directory_path}')
            return False  # 无法访问时不视为空目录
        except Exception as e:
            logger.error(f'检查目录是否为空时出错: {directory_path}, 错误: {str(e)}')
            return False

    def scan_empty_directories(self, 
                              folders: List[str], 
                              recursive: bool = True,
                              ignore_patterns: Optional[List[str]] = None,
                              max_depth: Optional[int] = None,
                              stop_event: Optional[Callable[[], bool]] = None) -> List[Dict[str, Any]]:
        """
        扫描空目录
        :param folders: 要扫描的根目录列表
        :param recursive: 是否递归扫描子目录
        :param ignore_patterns: 要忽略的文件/目录模式
        :param max_depth: 最大扫描深度
        :return: 空目录信息列表
        """
        if self._is_scanning:
            logger.warning('扫描已在进行中')
            return []

        self._is_scanning = True
        self._is_paused = False
        self._cancelled = False
        self._total_files = 0
        self._scanned_files = 0

        try:
            empty_dirs = []
            
            # 首先计算总的目录数量用于进度显示
            for folder in folders:
                if not os.path.exists(folder):
                    logger.warning(f'文件夹不存在: {folder}')
                    continue
                self._count_directories_for_empty_scan(folder, recursive, ignore_patterns, max_depth)

            # 扫描空目录
            for folder in folders:
                if not os.path.exists(folder):
                    continue
                empty_dirs.extend(self._scan_empty_directories_recursive(
                    folder, recursive, ignore_patterns, max_depth, 0, stop_event))

            logger.info(f'空目录扫描完成，共找到 {len(empty_dirs)} 个空目录')
            return empty_dirs

        finally:
            self._is_scanning = False

    def _count_directories_for_empty_scan(self, folder: str, recursive: bool, ignore_patterns: Optional[List[str]], max_depth: Optional[int] = None, current_depth: int = 0) -> None:
        """
        计算空目录扫描的总目录数（用于进度显示）
        """
        try:
            # 计算当前目录
            self._total_files += 1
            
            if recursive:
                for item in os.listdir(folder):
                    item_path = os.path.join(folder, item)
                    if self._should_ignore(item_path, ignore_patterns):
                        continue
                    if os.path.isdir(item_path):
                        if max_depth is None or current_depth < max_depth:
                            self._count_directories_for_empty_scan(item_path, recursive, ignore_patterns, max_depth, current_depth + 1)
        except PermissionError:
            logger.warning(f'无权限访问文件夹: {folder}')
        except Exception as e:
            logger.error(f'计算目录数量时出错: {str(e)}')

    def _scan_empty_directories_recursive(self, folder: str, recursive: bool, ignore_patterns: Optional[List[str]], max_depth: Optional[int] = None, current_depth: int = 0, stop_event: Optional[Callable[[], bool]] = None) -> List[Dict[str, Any]]:
        """
        递归扫描空目录
        """
        empty_dirs = []
        
        # 检查是否停止
        if self._cancelled or (stop_event and stop_event()):
            return empty_dirs

        while self._is_paused:
            if self._cancelled or (stop_event and stop_event()):
                return empty_dirs
            import time
            time.sleep(0.1)

        try:
            # 对于浅层扫描或第一层，检查目录下的直接子目录
            for item in os.listdir(folder):
                item_path = os.path.join(folder, item)
                
                # 检查是否需要忽略
                if self._should_ignore(item_path, ignore_patterns):
                    continue
                    
                if os.path.isdir(item_path):
                    # 检查这个子目录是否为空
                    if self._is_directory_empty(item_path, ignore_patterns):
                        # 确保路径使用Windows标准格式
                        normalized_item_path = os.path.normpath(item_path)
                        folder_stats = os.stat(normalized_item_path)
                        empty_dir_info = {
                            'path': normalized_item_path,
                            'name': os.path.basename(normalized_item_path),
                            'size': 0,  # 空目录大小为0
                            'modified_time': folder_stats.st_mtime,
                            'created_time': folder_stats.st_ctime,
                            'accessed_time': folder_stats.st_atime,
                            'type': 'empty_directory'
                        }
                        empty_dirs.append(empty_dir_info)
                        
                        # 更新进度
                        self._scanned_files += 1
                        if self.progress_callback:
                            self.progress_callback(self._scanned_files, self._total_files)
                        if self.file_callback:
                            self.file_callback(empty_dir_info)
                    
                    # 如果需要递归扫描且未达到深度限制，继续扫描子目录
                    elif recursive and (max_depth is None or current_depth < max_depth):
                        sub_empty_dirs = self._scan_empty_directories_recursive(
                            item_path, recursive, ignore_patterns, max_depth, current_depth + 1)
                        empty_dirs.extend(sub_empty_dirs)
                        
            # 更新进度（完成当前目录的扫描）
            self._scanned_files += 1
            if self.progress_callback:
                self.progress_callback(self._scanned_files, self._total_files)

        except PermissionError:
            logger.warning(f'无权限访问文件夹: {folder}')
            # 权限错误也要更新进度
            self._scanned_files += 1
            if self.progress_callback:
                self.progress_callback(self._scanned_files, self._total_files)
        except Exception as e:
            logger.error(f'扫描空目录时出错: {folder}, 错误: {str(e)}')
            # 错误情况也要更新进度
            self._scanned_files += 1
            if self.progress_callback:
                self.progress_callback(self._scanned_files, self._total_files)

        return empty_dirs

    def _get_folder_size(self, folder: str, ignore_patterns: Optional[List[str]]) -> int:
        """计算文件夹的总大小
        :param folder: 文件夹路径
        :param ignore_patterns: 要忽略的文件模式列表
        :return: 文件夹总大小（字节）
        """
        total_size = 0
        try:
            for item in os.listdir(folder):
                item_path = os.path.join(folder, item)
                if self._should_ignore(item_path, ignore_patterns):
                    continue
                if os.path.isfile(item_path):
                    try:
                        total_size += os.path.getsize(item_path)
                    except (OSError, FileNotFoundError) as e:
                        logger.error(f'获取文件大小错误: {item_path}, 错误: {str(e)}')
                elif os.path.isdir(item_path):
                    subdir_size = self._get_folder_size(item_path, ignore_patterns)
                    if subdir_size is not None:  # 检查子目录大小是否为有效值
                        total_size += subdir_size
        except Exception as e:
            logger.error(f'计算文件夹大小错误: {folder}, 错误: {str(e)}')
            return 0  # 发生错误时返回0而不None
        return total_size

    def _get_folder_info(self, folder: str, ignore_patterns: Optional[List[str]]) -> Optional[Dict[str, Any]]:
        """获取文件夹信息
        :param folder: 文件夹路径
        :param ignore_patterns: 要忽略的文件模式列表
        :return: 文件夹信息字典
        """
        try:
            # 确保路径使用Windows标准格式
            normalized_folder = os.path.normpath(folder)
            folder_stats = os.stat(normalized_folder)
            size = self._get_folder_size(normalized_folder, ignore_patterns)
            return {
                'path': normalized_folder,
                'name': os.path.basename(normalized_folder),
                'size': size,
                'modified_time': folder_stats.st_mtime,
                'created_time': folder_stats.st_ctime,
                'accessed_time': folder_stats.st_atime,
                'type': 'directory'
            }
        except Exception as e:
            logger.error(f'获取文件夹信息时出错: {str(e)}')
            return None

    def start_scan(self,
                   folders: List[str],
                   recursive: bool = True,
                   ignore_patterns: Optional[List[str]] = None,
                   scan_dirs_as_files: bool = False,
                   scan_empty_dirs: bool = False,
                   max_depth: Optional[int] = None,
                   stop_event: Optional[Callable[[], bool]] = None) -> List[Dict[str, Any]]:
        """
        开始扫描文件
        :param folders: 要扫描的文件夹列表
        :param recursive: 是否递归扫描子文件夹
        :param ignore_patterns: 要忽略的文件模式列表
        :param scan_dirs_as_files: 是否将目录作为文件扫描
        :param scan_empty_dirs: 是否扫描空目录
        :param max_depth: 最大扫描深度（None表示无限制）
        :return: 文件信息列表
        """
        if self._is_scanning:
            logger.warning('扫描已在进行中')
            return []

        # 如果是空目录扫描模式，直接调用专门的方法
        if scan_empty_dirs:
            return self.scan_empty_directories(folders, recursive, ignore_patterns, max_depth, stop_event)

        self._is_scanning = True
        self._is_paused = False
        self._cancelled = False
        self._total_files = 0
        self._scanned_files = 0

        try:
            self._scanned_dirs = []  # 重置目录列表
            # 首先计算总文件数
            for folder in folders:
                # 检查是否停止
                if self._cancelled or (stop_event and stop_event()):
                    logger.info('扫描已取消或停止')
                    return []
                
                if not os.path.exists(folder):
                    logger.warning(f'文件夹不存在: {folder}')
                    continue
                if scan_dirs_as_files:
                    # 目录模式：计算目录数量
                    self._count_directories(folder, recursive, ignore_patterns, max_depth, stop_event)
                else:
                    # 文件模式：计算文件数量
                    self._count_files(folder, recursive, ignore_patterns, max_depth, stop_event)

            # 然后扫描文件和目录
            file_info_list = []
            for folder in folders:
                if not os.path.exists(folder):
                    continue
                if scan_dirs_as_files:
                    # 目录模式：获取文件夹信息并添加到结果
                    folder_info = self._get_folder_info(folder, ignore_patterns)
                    if folder_info and folder_info['size'] > 0:
                        self._scanned_dirs.append(folder_info)
                        file_info_list.append(folder_info)
                        self._scanned_files += 1
                        if self.progress_callback:
                            self.progress_callback(self._scanned_files, self._total_files)
                        if self.file_callback:
                            self.file_callback(folder_info)
                    # 目录模式下扫描子目录作为目录项而非文件
                    sub_dirs = self._scan_directories(folder, recursive, ignore_patterns, max_depth)
                    file_info_list.extend(sub_dirs)
                else:
                    # 文件模式：扫描文件
                    files = self._scan_folder(folder, recursive, ignore_patterns, max_depth)
                    file_info_list.extend(files)

            logger.info(f'扫描完成，共扫描 {self._scanned_files} 个项目 ({len(self._scanned_dirs)} 个目录, {self._scanned_files - len(self._scanned_dirs)} 个文件)')
            return file_info_list

        finally:
            self._is_scanning = False

    def pause_scan(self) -> None:
        """暂停扫描"""
        if self._is_scanning and not self._is_paused:
            self._is_paused = True
            logger.info('扫描已暂停')

    def resume_scan(self) -> None:
        """恢复扫描"""
        if self._is_scanning and self._is_paused:
            self._is_paused = False
            logger.info('扫描已恢复')

    def cancel_scan(self) -> None:
        """取消扫描"""
        if self._is_scanning:
            self._cancelled = True
            logger.info('扫描已取消')

    def _count_files(self, folder: str, recursive: bool, ignore_patterns: Optional[List[str]], max_depth: Optional[int] = None, current_depth: int = 0, stop_event: Optional[Callable[[], bool]] = None) -> None:
        """计算文件夹中的文件数量"""
        try:
            # 检查是否停止
            if self._cancelled or (stop_event and stop_event()):
                return
            
            for item in os.listdir(folder):
                # 检查是否停止
                if self._cancelled or (stop_event and stop_event()):
                    return
                
                item_path = os.path.join(folder, item)

                # 检查是否需要忽略
                if self._should_ignore(item_path, ignore_patterns):
                    continue

                if os.path.isfile(item_path):
                    self._total_files += 1
                elif recursive and os.path.isdir(item_path):
                    # 检查深度限制
                    if max_depth is None or current_depth < max_depth:
                        self._count_files(item_path, recursive, ignore_patterns, max_depth, current_depth + 1, stop_event)
        except PermissionError:
            logger.warning(f'无权限访问文件夹: {folder}')
        except Exception as e:
            logger.error(f'计算文件数量时出错: {str(e)}')

    def _count_directories(self, folder: str, recursive: bool, ignore_patterns: Optional[List[str]], max_depth: Optional[int] = None, current_depth: int = 0, stop_event: Optional[Callable[[], bool]] = None) -> None:
        """计算文件夹中的目录数量"""
        try:
            # 检查是否停止
            if self._cancelled or (stop_event and stop_event()):
                return
            
            # 添加当前目录到总数
            folder_size = self._get_folder_size(folder, ignore_patterns)
            if folder_size > 0:  # 只添加非空文件夹
                self._total_files += 1
            
            # 递归计算子目录
            if recursive:
                for item in os.listdir(folder):
                    # 检查是否停止
                    if self._cancelled or (stop_event and stop_event()):
                        return
                    
                    item_path = os.path.join(folder, item)

                    # 检查是否需要忽略
                    if self._should_ignore(item_path, ignore_patterns):
                        continue

                    if os.path.isdir(item_path):
                        # 检查深度限制
                        if max_depth is None or current_depth < max_depth:
                            self._count_directories(item_path, recursive, ignore_patterns, max_depth, current_depth + 1, stop_event)
        except PermissionError:
            logger.warning(f'无权限访问文件夹: {folder}')
        except Exception as e:
            logger.error(f'计算目录数量时出错: {str(e)}')

    def _scan_folder(self, folder: str, recursive: bool, ignore_patterns: Optional[List[str]], max_depth: Optional[int] = None, current_depth: int = 0, stop_event: Optional[Callable[[], bool]] = None) -> List[Dict[str, Any]]:
        """扫描文件夹并返回文件信息列表"""
        file_info_list = []

        # 检查是否停止
        if self._cancelled or (stop_event and stop_event()):
            return file_info_list

        while self._is_paused:
            if self._cancelled or (stop_event and stop_event()):
                return file_info_list
            import time
            time.sleep(0.1)

        try:
            for item in os.listdir(folder):
                # 检查是否停止
                if self._cancelled or (stop_event and stop_event()):
                    return file_info_list
                
                item_path = os.path.join(folder, item)

                # 检查是否需要忽略
                if self._should_ignore(item_path, ignore_patterns):
                    continue

                if os.path.isfile(item_path):
                    file_info = self._get_file_info(item_path)
                    if file_info:
                        file_info_list.append(file_info)
                        self._scanned_files += 1
                        if self.progress_callback:
                            self.progress_callback(self._scanned_files, self._total_files)
                        if self.file_callback:
                            self.file_callback(file_info)
                elif recursive and os.path.isdir(item_path):
                    # 检查深度限制
                    if max_depth is None or current_depth < max_depth:
                        sub_files = self._scan_folder(item_path, recursive, ignore_patterns, max_depth, current_depth + 1, stop_event)
                        file_info_list.extend(sub_files)



        except PermissionError:
            logger.warning(f'无权限访问文件夹: {folder}')
        except Exception as e:
            logger.error(f'扫描文件夹时出错: {str(e)}')

        return file_info_list

    def _scan_directories(self, folder: str, recursive: bool, ignore_patterns: Optional[List[str]], max_depth: Optional[int] = None, current_depth: int = 0) -> List[Dict[str, Any]]:
        """扫描文件夹并返回目录信息列表（仅目录）"""
        dir_info_list = []

        if self._cancelled:
            return dir_info_list

        while self._is_paused:
            if self._cancelled:
                return dir_info_list
            import time
            time.sleep(0.1)

        try:
            for item in os.listdir(folder):
                item_path = os.path.join(folder, item)

                # 检查是否需要忽略
                if self._should_ignore(item_path, ignore_patterns):
                    continue

                if os.path.isdir(item_path):
                    # 获取目录信息
                    dir_info = self._get_folder_info(item_path, ignore_patterns)
                    if dir_info:
                        self._scanned_dirs.append(dir_info)
                        dir_info_list.append(dir_info)
                        self._scanned_files += 1
                        if self.progress_callback:
                            self.progress_callback(self._scanned_files, self._total_files)
                        if self.file_callback:
                            self.file_callback(dir_info)
                    
                    # 递归扫描子目录
                    if recursive:
                        # 检查深度限制
                        if max_depth is None or current_depth < max_depth:
                            sub_dirs = self._scan_directories(item_path, recursive, ignore_patterns, max_depth, current_depth + 1)
                            dir_info_list.extend(sub_dirs)

                if self._cancelled:
                    break

        except PermissionError:
            logger.warning(f'无权限访问文件夹: {folder}')
        except Exception as e:
            logger.error(f'扫描文件夹时出错: {str(e)}')

        return dir_info_list

    def _get_file_info(self, file_path: str) -> Optional[Dict[str, Any]]:
        """获取文件信息"""
        try:
            # 确保路径使用Windows标准格式
            normalized_path = os.path.normpath(file_path)
            file_stats = os.stat(normalized_path)
            return {
                'path': normalized_path,
                'name': os.path.basename(normalized_path),
                'size': file_stats.st_size,
                'modified_time': file_stats.st_mtime,
                'created_time': file_stats.st_ctime,
                'accessed_time': file_stats.st_atime
            }
        except Exception as e:
            logger.error(f'获取文件信息时出错: {str(e)}')
            return None

    def _should_ignore(self, path: str, ignore_patterns: Optional[List[str]]) -> bool:
        """检查路径是否应该被忽略"""
        if not ignore_patterns:
            return False

        path_lower = path.lower()
        for pattern in ignore_patterns:
            pattern_lower = pattern.lower()
            # 检查是否是文件夹忽略模式
            if pattern_lower.endswith('/') and os.path.isdir(path):
                if path_lower.endswith(pattern_lower) or path_lower == pattern_lower[:-1]:
                    return True
            # 检查是否是文件扩展名忽略模式
            elif pattern_lower.startswith('.'):
                if path_lower.endswith(pattern_lower):
                    return True
            # 检查是否是包含模式
            elif pattern_lower in path_lower:
                return True

        return False


if __name__ == '__main__':
    # 测试扫描器
    def progress_callback(scanned: int, total: int):
        print(f'已扫描: {scanned}/{total} 文件')

    def file_callback(file_info: Dict[str, Any]):
        print(f'发现文件: {file_info["path"]}, 大小: {file_info["size"]} 字节')

    scanner = Scanner(progress_callback=progress_callback, file_callback=file_callback)
    folders = ['.']  # 扫描当前目录
    ignore_patterns = ['.git/', '.trae/', '*.log', 'tmp/']
    files = scanner.start_scan(folders, recursive=True, ignore_patterns=ignore_patterns)
    print(f'扫描完成，共找到 {len(files)} 个文件')